<?php
/**
 * KePHP, Keep PHP easy!
 *
 * @license   https://opensource.org/licenses/MIT
 * @copyright Copyright 2015-2018 KePHP Authors All Rights Reserved
 * @link      http://kephp.com/utils ( https://git.oschina.net/kephp/kephp-utils )
 * @author    曾建凯 <janpoem@163.com>
 */

namespace Ke\TestUtils;

/**
 * 测试用例，用以维持测试所需的excepted值，和将要调用测试方法的参数组
 *
 * @package Ke\TestUtils
 * @property mixed  $excepted
 * @property mixed  $actual
 * @property array  $args
 * @property string $message
 */
class TestItem
{
	
	const INIT = -1;
	const VALUE_HOLDER = 0;
	const CUSTOM_TEST = 1;
	
	private $type = self::INIT;
	
	/** @var TestMethod */
	private $method = null;
	
	/** @var \Closure */
	private $customTestClosure = null;
	
	private $excepted = null;
	
	private $actual = null;
	
	private $isInvoke = false;
	
	private $message = '';
	
	private $args = [];
	
	private $accessProps = [
		'excepted' => 'getExcepted',
		'actual'   => 'getActual',
		'message'  => 'getMessage',
		'args'     => 'getArgs',
	];
	
	/**
	 * 生成测试用例的工厂方法
	 *
	 * @param mixed       $excepted 例外值
	 * @param mixed       $args     要被用于调用的参数组
	 * @param string|null $message  测试的消息文字
	 * @return \Ke\TestUtils\TestItem
	 */
	public static function factory($excepted = null, $args = null, string $message = null)
	{
		return new static($excepted, $args, $message);
	}
	
	/**
	 * TestItem 构造函数
	 *
	 * @param null             $excepted
	 * @param mixed|array|null $args
	 * @param string|null      $message
	 */
	public function __construct($excepted = null, $args = null, string $message = null)
	{
		$this->setExcepted($excepted);
		$this->message = $message ?? '';
		$this->setArgs($args);
	}
	
	public function __get($name)
	{
		if (isset($this->accessProps[$name])) {
			return $this->{$this->accessProps[$name]}();
		}
		return null;
	}
	
	/**
	 * 设置调用测试方法的参数
	 *
	 * @param null|array|mixed $args
	 * @return $this
	 */
	public function setArgs($args = null)
	{
		if (!isset($args)) $args = [];
		if ($args instanceof \ArrayObject) $args = $args->getArrayCopy();
		if (!is_array($args)) $args = [$args];
		$this->args = $args;
		return $this;
	}
	
	/**
	 * 取出当前测试用例的参数组
	 *
	 * @return array
	 */
	public function getArgs(): array
	{
		return $this->args;
	}
	
	/**
	 * 绑定测试方法，不是必须，可通过invoke传入
	 *
	 * @param \Ke\TestUtils\TestMethod $method
	 * @return $this
	 */
	public function setMethod(TestMethod $method)
	{
		if (!isset($this->method))
			$this->method = $method;
		return $this;
	}
	
	/**
	 * 当前的测试用例是否为一个自定义测试
	 *
	 * @return bool
	 */
	public function isCustomTest(): bool
	{
		return $this->type === self::CUSTOM_TEST;
	}
	
	/**
	 * 设置当前用例的Excepted值
	 *
	 * 测试用例初始化时，可依据excepted传入类型，决定该测试用例的类型。
	 *
	 * 再次对测试用例修改Excepted值时，只允许朝明确的类型进行修改，不允许切换类型。
	 *
	 * @param mixed $excepted
	 * @return $this
	 */
	public function setExcepted($excepted)
	{
		if (!$this->isInvoke) {
			if ($this->type === self::INIT) {
				if ($excepted instanceof \Closure) {
					$this->type = self::CUSTOM_TEST;
					$this->customTestClosure = $excepted;
					$this->excepted = null;
				} else {
					$this->type = self::VALUE_HOLDER;
					$this->excepted = $excepted;
					$this->customTestClosure = null;
				}
			} else if ($this->type === self::CUSTOM_TEST) {
				if ($excepted instanceof \Closure) {
					$this->customTestClosure = $excepted;
					$this->excepted = null;
				}
			} else if ($this->type === self::VALUE_HOLDER) {
				$this->excepted = $excepted;
				$this->customTestClosure = null;
			}
		}
		return $this;
	}
	
	/**
	 * 取得测试用例的Excepted值
	 *
	 * @return null
	 */
	public function getExcepted()
	{
		if ($this->isCustomTest()) {
			return null;
		}
		return $this->excepted;
	}
	
	/**
	 * 取得测试用例的Actual值
	 *
	 * @return null
	 */
	public function getActual()
	{
		if ($this->isCustomTest()) {
			return null;
		}
		return $this->actual;
	}
	
	/**
	 * 取得测试的提示消息
	 *
	 * @return string
	 */
	public function getMessage(): string
	{
		return $this->message ?? '';
	}
	
	/**
	 * 该测试用例是否已被执行
	 *
	 * @return bool
	 */
	public function isInvoke(): bool
	{
		return $this->isInvoke;
	}
	
	/**
	 * 以当前的测试用例去执行一个测试方法
	 *
	 * @param TestMethod|null $method
	 * @return mixed
	 */
	public function invoke(TestMethod $method = null)
	{
		if (!$this->isInvoke) {
			if ($this->isCustomTest()) {
				$this->isInvoke = true;
				// 调用这个函数，但不直接返回这个结果
				// 仍然将TestMethod传递过去
				call_user_func($this->customTestClosure, $method ?? $this->method);
			} else {
				$method = $method ?? $this->method;
				if (isset($method)) {
					$this->isInvoke = true;
					$this->actual = $this->method->invoke(...$this->args);
				}
			}
		}
		return $this->getActual();
	}
}